/*
    RoleCall provides a web service that web applications can use to find 
    the roles associated with a user that has been authenticated via OpenID. 
    Also provides a web-based user interface to manage the user roles 
    associated with partner web applications.
    
    Development of this software was supported in part by the David and Lucile 
    Packard Foundation and by the the Office Of Naval Research (ONR) 
     
    Copyright (c) 2012, 
    Monterey Bay Aquarium Research Institute - MBARI ( www.mbari.org )
    Michael Godin ( mikegodin AT users DOT sourceforge DOT net )

    RoleCall is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    RoleCall is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foobar (see the file COPYING.GPL).  If not, see 
    <http://www.gnu.org/licenses/>.  
*/

package org.mbari.rolecall.client;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.mbari.rolecall.client.composite.EditRelay;
import org.mbari.rolecall.client.widget.OnFailure;
import org.mbari.rolecall.client.widget.SpacedFlowPanel;
import org.mbari.rolecall.client.widget.VerticalFlowPanel;
import org.mbari.rolecall.client.widget.WidgetUtil;
import org.mbari.rolecall.model.Provider;
import org.mbari.rolecall.model.Relay;
import org.mbari.rolecall.model.RelayUserRole;
import org.mbari.rolecall.model.Role;
import org.mbari.rolecall.model.Status;
import org.mbari.rolecall.model.User;
import org.mbari.rolecall.model.UserRole;
import org.mbari.rolecall.model.UserRoleMatch;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Cookies;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Hidden;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public class RoleCall implements EntryPoint, RoleLister {

    protected static Date nextYear() {
        return new Date((new Date()).getTime() + 86400L * 1000L * 365L);
    }

    EditRelay editRelay;

    private CheckBox keepLoginBox = null;

    private DialogBox loginDialog;

    protected Button logInOutButton;

    Map<Integer, RelayUserRole> relayMap = new HashMap<Integer, RelayUserRole>();

    final FlowPanel relayPanel = new VerticalFlowPanel();

    private final RoleCallServiceAsync service = GWT
            .create(RoleCallService.class);

    protected Status status;

    User user;

    protected Label welcomeText;

    protected void administerRelay(Relay relay) {
        editRelay.edit(relay);
    }

    private void constructLoginDialog() {
        loginDialog = new DialogBox(false, true);
        loginDialog.setText("Login via:");

        final VerticalPanel vPanel = new VerticalPanel();
        loginDialog.add(vPanel);

        keepLoginBox = new CheckBox("Keep me logged in.");
        String keepLoginStr = Cookies.getCookie("openid_identifier");
        keepLoginBox
                .setValue(null == keepLoginStr || keepLoginStr.length() > 0);
        vPanel.add(keepLoginBox);

        service.getProviders(new AsyncCallback<List<Provider>>() {

            @Override
            public void onFailure(Throwable caught) {
                // TODO Auto-generated method stub

            }

            @Override
            public void onSuccess(List<Provider> providerList) {
                if (null != providerList) {
                    for (final Provider provider : providerList) {
                        vPanel.add(new Button(
                                "<img style='width:16px;height:16px' src='"
                                        + provider.getFaviconURL() + "'/> <b>"
                                        + provider.getName() + "</b>",
                                new ClickHandler() {

                                    @Override
                                    public void onClick(ClickEvent event) {
                                        loginOpenId(provider.getUrl(),
                                                provider.getPrompt());
                                    }
                                }));
                    }
                }
            }
        });
    }

    protected void handleStatus(Status status) {
        if (null == this.status) {
            user = status.getUser();
            if (null == user) {
                Cookies.removeCookie("openid_identifier", "/");
                welcomeText.setText("Please log in");
                logInOutButton.setText("Login");
                logInOutButton.addClickHandler(new ClickHandler() {
                    @Override
                    public void onClick(ClickEvent event) {
                        showLoginDialog();
                    }
                });
            } else {
                welcomeText.setText("Welcome " + user.getName() + " <"
                        + user.getEmail() + "> (" + user.getOpenId() + ")");
                logInOutButton.setText("Logout");
                logInOutButton.addClickHandler(new ClickHandler() {
                    @Override
                    public void onClick(ClickEvent event) {
                        logout();
                    }
                });
            }
        }
        this.status = status;
    }

    protected void listRelayUserRoles(List<RelayUserRole> relayUserRoleList) {
        if (null != relayUserRoleList) {
            for (RelayUserRole relayUserRole : relayUserRoleList) {
                final Relay relay = relayUserRole.getRelay();
                relayMap.put(relay.getId(), relayUserRole);
                FlowPanel relayFlow = new SpacedFlowPanel();
                relayPanel.add(relayFlow);
                InlineLabel relayLabel = new InlineLabel(relay.getName()
                        + " Roles:");
                WidgetUtil.setWeightBold(relayLabel);
                relayFlow.add(relayLabel);
                for (Integer roleId : relayUserRole.getRoleIds()) {
                    Role role = RoleMap.get(roleId);
                    String roleName = role.getName();
                    if (roleName.equals("roleCallRelayAdmin")) {
                        Anchor adminAnchor = new Anchor(roleName, "javascript:");
                        adminAnchor.addClickHandler(new ClickHandler() {
                            @Override
                            public void onClick(ClickEvent event) {
                                administerRelay(relay);
                            }
                        });
                        relayFlow.add(adminAnchor);
                    } else {
                        relayFlow.add(new InlineLabel(roleName));
                    }
                }
            }
        }
    }

    protected void listRoles(List<Role> roleList) {
        if (null != roleList) {
            for (Role role : roleList) {
                RoleMap.put(role);
            }
        }
        service.listRelayUserRoles(new AsyncCallback<List<RelayUserRole>>() {
            @Override
            public void onFailure(Throwable caught) {
                OnFailure.report("Error listing roles", caught);
            }

            @Override
            public void onSuccess(List<RelayUserRole> result) {
                listRelayUserRoles(result);
            }
        });
    }

    @Override
    public void listUserRole(UserRole userRole) {
        if (null == user || !userRole.getEmail().equals(user.getEmail())) {
            return;
        }
        FlowPanel relayFlow = null;
        for (int i = 0; i < relayPanel.getWidgetCount(); ++i) {
            FlowPanel aRelayFlow = (FlowPanel) relayPanel.getWidget(i);
            InlineLabel relayLabel = (InlineLabel) aRelayFlow.getWidget(0);
            if (relayLabel.getText().equals(userRole.getRelay().getName())) {
                relayFlow = aRelayFlow;
                break;
            }
        }
        final Relay relay = userRole.getRelay();
        if (null == relayFlow) {
            relayFlow = new SpacedFlowPanel();
            relayPanel.add(relayFlow);
            InlineLabel relayLabel = new InlineLabel(relay.getName()
                    + " Roles:");
            WidgetUtil.setWeightBold(relayLabel);
            relayFlow.add(relayLabel);
        }
        String roleName = userRole.getRole().getName();
        if (roleName.equals("roleCallRelayAdmin")) {
            Anchor adminAnchor = new Anchor(roleName, "javascript:");
            adminAnchor.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
                    administerRelay(relay);
                }
            });
            relayFlow.add(adminAnchor);
        } else {
            relayFlow.add(new InlineLabel(roleName));
        }
    }

    @Override
    public void listUserRoleMatch(UserRoleMatch result) {
        // Do nothing.
    }

    private void loginOpenId(String openIdUrl, String prompt) {

        if (null != prompt && prompt.length() > 0) {
            String promptResult = Window.prompt(prompt, "");
            if (null != promptResult) {
                openIdUrl = openIdUrl.replace("%s", promptResult);
            } else {
                return;
            }
        }

        if (null != keepLoginBox && keepLoginBox.getValue()) {
            Cookies.setCookie("openid_identifier", openIdUrl, nextYear(), null,
                    "/", false);
        }

        FormPanel form = new FormPanel("_self");
        form.setMethod(FormPanel.METHOD_POST);
        form.setAction(Window.Location.getHref());
        RootPanel.get().add(form);
        form.add(new Hidden("openid_identifier", openIdUrl));
        form.submit();
    }

    protected void logout() {
        Cookies.removeCookie("openid_identifier", "/");
        FormPanel form = new FormPanel("_self");
        form.setMethod(FormPanel.METHOD_POST);
        form.setAction(Window.Location.getHref());
        RootPanel.get().add(form);
        form.add(new Hidden("logout", "true"));
        form.submit();
    }

    public void onModuleLoad() {

        service.getStatus(new AsyncCallback<Status>() {
            @Override
            public void onFailure(Throwable caught) {
                OnFailure.report("Error getting user", caught);
            }

            @Override
            public void onSuccess(Status status) {
                handleStatus(status);
            }
        });

        welcomeText = new Label();
        RootPanel.get().add(welcomeText);

        logInOutButton = new Button();
        RootPanel.get().add(logInOutButton);

        RootPanel.get().add(new HTML("<hr/>"));
        RootPanel.get().add(relayPanel);
        RootPanel.get().add(new HTML("<hr/>"));

        editRelay = new EditRelay(service, this);
        RootPanel.get().add(editRelay);

        service.listRoles(new AsyncCallback<List<Role>>() {
            @Override
            public void onFailure(Throwable caught) {
                OnFailure.report("Error listing roles", caught);
            }

            @Override
            public void onSuccess(List<Role> result) {
                listRoles(result);
            }
        });
    }

    @Override
    public void removeUserRole(UserRole userRole) {
        if (null == user || !userRole.getEmail().equals(user.getEmail())) {
            return;
        }
        FlowPanel relayFlow = null;
        for (int i = 0; i < relayPanel.getWidgetCount(); ++i) {
            FlowPanel aRelayFlow = (FlowPanel) relayPanel.getWidget(i);
            InlineLabel relayLabel = (InlineLabel) aRelayFlow.getWidget(0);
            if (relayLabel.getText().equals(userRole.getRelay().getName())) {
                relayFlow = aRelayFlow;
                break;
            }
        }
        if (null == relayFlow) {
            return;
        }
        String roleName = userRole.getRole().getName();
        for (int i = 2; i < relayFlow.getWidgetCount(); ++i) {
            Widget widget = relayFlow.getWidget(i);
            if (widget instanceof Anchor) {
                Anchor anchor = (Anchor) widget;
                if (anchor.getText().equals(roleName)) {
                    relayFlow.remove(i);
                    break;
                }
            }
            if (widget instanceof InlineLabel) {
                InlineLabel label = (InlineLabel) widget;
                if (label.getText().equals(roleName)) {
                    relayFlow.remove(i);
                    break;
                }
            }
        }
    }

    @Override
    public void removeUserRoleMatch(UserRoleMatch result) {
        // Never called.
    }

    protected void showLoginDialog() {
        if (null == loginDialog) {
            constructLoginDialog();
        }
        loginDialog.center();
    }
}
